// Progress reporting datapumps
// Copyright 2006 (C) Ralph Thomas

#include <datapumps/progress.h>
#include <toolkit/application.h>
#include <boost/shared_ptr.hpp>
#include <adobe/once.hpp>
#include <binder.hpp>
#include <map>

namespace datapumps {
	ADOBE_GLOBAL_MUTEX_DEFINITION(progresso)
	ADOBE_ONCE_DECLARATION(progress)
	ADOBE_ONCE_STATIC_INSTANCE(progress)

	struct progress_data_t {
		progress_data_t() : count_m(100), progress_m(0) {}
		std::string		name_m;
		std::string		url_m;
		unsigned int	count_m;
		unsigned int	progress_m;
	};
	class progress_t {
		typedef boost::signal<void()>					update_signal_t;
		typedef std::map<unsigned int, progress_data_t> progress_map_t;
		progress_map_t	progress_m;
		update_signal_t	signal_m;
		void update() { signal_m(); }
		inline void notify() {
			toolkit::application::getInstance().executeInMain(boost::bind(&progress_t::update, this));
		}
	  public:
		typedef boost::signals::connection			connection_t;
		typedef boost::signals::scoped_connection	scoped_connection_t;
		progress_t() {}
		unsigned int new_id(const std::string& name) {
			ADOBE_GLOBAL_MUTEX_INSTANCE(progresso);
			unsigned int id(0);
			for (progress_map_t::const_iterator i = progress_m.begin(); i != progress_m.end(); i++)
				id = std::max(id, i->first + 1);
			progress_m[id].name_m = name;
			//
			// Notify the main thread.
			//
			notify();
			return id;
		}
		void complete(unsigned int id) {
			ADOBE_GLOBAL_MUTEX_INSTANCE(progresso);
			progress_m.erase(progress_m.find(id));
			notify();
		}
		void set_count(unsigned int id, unsigned int count) {
			ADOBE_GLOBAL_MUTEX_INSTANCE(progresso);
			progress_map_t::iterator i(progress_m.find(id));
			i->second.count_m = count;
			notify();
		}
		void state_progress(unsigned int id, unsigned int progress, const std::string& url) {
			ADOBE_GLOBAL_MUTEX_INSTANCE(progresso);
			progress_map_t::iterator i(progress_m.find(id));
			i->second.url_m = url;
			i->second.progress_m = progress;
			notify();
		}
		progress_data_t get_data(unsigned int id) {
			ADOBE_GLOBAL_MUTEX_INSTANCE(progresso);
			return progress_m.find(id)->second;
		}
		adobe::array_t get_ids() {
			ADOBE_GLOBAL_MUTEX_INSTANCE(progresso);
			adobe::array_t ids;
			for (progress_map_t::const_iterator i = progress_m.begin(); i != progress_m.end(); i++ )
				ids.push_back(adobe::value_t(i->first));
			return ids;
		}
		connection_t monitor(const boost::function<void()>& fn) {
			return signal_m.connect(fn);
		}
	};
	typedef boost::shared_ptr<progress_t> progress_ptr_t;
	progress_t*	progress_s;
	static void init_progress() {
		progress_s = new progress_t();
	}
	ADOBE_ONCE_DEFINITION(progress, init_progress)

	progress_report_t::progress_report_t(const std::string& name) : id_m(0) {
		id_m = progress_s->new_id(name);
	}
	progress_report_t::~progress_report_t() {
		progress_s->complete(id_m);
	}
	void progress_report_t::set_count(unsigned int count) {
		progress_s->set_count(id_m, count);
	}
	void progress_report_t::state_progress(unsigned int progress, const std::string& url) {
		progress_s->state_progress(id_m, progress, url);
	}
	//
	/// Datapumps implementation.
	//
	struct progress_items_datapump_t : public adobe::binder_t {
		adobe::sheet_t::monitor_value_t		value_monitor_m;
		progress_t::scoped_connection_t		connection_m;
		void update() {
			if (!value_monitor_m) return;
			value_monitor_m(adobe::value_t(progress_s->get_ids()));
		}
	  public:
		progress_items_datapump_t() {
			connection_m = progress_s->monitor( boost::bind( &progress_items_datapump_t::update, this ) );
		}
		~progress_items_datapump_t() {}
		void set( const adobe::value_t& ) {}
		void set_signals( const adobe::sheet_t::monitor_value_t& value_monitor, const adobe::sheet_t::monitor_active_t& active_monitor ) {
			value_monitor_m = value_monitor;
			if (active_monitor) active_monitor(false);
			update();
		}
		void substitute( const adobe::name_t&, const adobe::value_t& ) {}
		void invoke() {}
		adobe::binder_ptr_t copy() const {
			return adobe::binder_ptr_t( new progress_items_datapump_t );
		}
	};
	adobe::value_t make_progress_items_datapump() {
		return adobe::value_t(adobe::binder_ptr_t( new progress_items_datapump_t ));
	}
	struct progress_datapump_t : public adobe::binder_t {
		typedef boost::function<adobe::value_t (const progress_data_t&)> getter_proc_t;

		adobe::sheet_t::monitor_value_t		value_monitor_m;
		progress_t::scoped_connection_t		connection_m;
		unsigned int						id_m;
		bool								valid_id_m;
		getter_proc_t						getter_m;
		adobe::binder_helper_t				id_datapump_m;
		//
		// for copying
		//
		adobe::sheet_t&						sheet_m;
		adobe::value_t						value_m;

		void update_id(const adobe::value_t& v) {
			id_m = v.get<unsigned int>();
			valid_id_m = true;
			update();
		}
		void update() {
			if (!valid_id_m || !value_monitor_m || !getter_m) return;
			value_monitor_m( getter_m( progress_s->get_data(id_m) ) );
		}
	  public:
		progress_datapump_t( const getter_proc_t& getter, const adobe::value_t& v, adobe::sheet_t& sheet ) : id_m(0), valid_id_m(false), getter_m(getter), id_datapump_m(sheet), sheet_m(sheet), value_m(v) {
			connection_m = progress_s->monitor( boost::bind( &progress_datapump_t::update, this ) );
			id_datapump_m.initialize( v, boost::bind( &progress_datapump_t::update_id, this, _1 ), 0 );
		}
		~progress_datapump_t() {}
		void set( const adobe::value_t& ) {}
		void set_signals( const adobe::sheet_t::monitor_value_t& value_monitor, const adobe::sheet_t::monitor_active_t& active_monitor ) {
			value_monitor_m = value_monitor;
			if (active_monitor) active_monitor(false);
			update();
		}
		void substitute( const adobe::name_t& name, const adobe::value_t& value ) {
			id_datapump_m.substitute( name, value );
		}
		void invoke() {}
		adobe::binder_ptr_t copy() const {
			return adobe::binder_ptr_t( new progress_datapump_t( getter_m, value_m, sheet_m ) );
		}
	};
	static adobe::value_t get_url( const progress_data_t& pdata ) { return adobe::value_t( pdata.url_m ); }
	adobe::value_t make_progress_url_datapump( const adobe::array_t& array, adobe::sheet_t& sheet ) {
		return adobe::value_t( adobe::binder_ptr_t( new progress_datapump_t( &get_url, array.at(0), sheet ) ) );
	}
	static adobe::value_t get_name( const progress_data_t& pdata ) { return adobe::value_t( pdata.name_m ); }
	adobe::value_t make_progress_name_datapump( const adobe::array_t& array, adobe::sheet_t& sheet ) {
		return adobe::value_t( adobe::binder_ptr_t( new progress_datapump_t( &get_name, array.at(0), sheet ) ) );
	}
	static adobe::value_t get_progress( const progress_data_t& pdata ) {
		if (pdata.count_m == 0 || pdata.count_m <= pdata.progress_m)
			return adobe::value_t(100);
		return adobe::value_t((pdata.progress_m * 100) / pdata.count_m);
	}
	adobe::value_t make_progress_percentage_datapump( const adobe::array_t& array, adobe::sheet_t& sheet ) {
		return adobe::value_t( adobe::binder_ptr_t( new progress_datapump_t( &get_progress, array.at(0), sheet ) ) );
	}
}

